1. fseek()을 이용한 파일 포인터(오프셋) 위치 제어

  • fseek() 함수는 파일 내부에서 다음에 읽거나 쓸 위치를 가리키는 파일 오프셋(File Offset)을 원하는 지점으로 이동시킵니다.

  • SEEK_SET (0): 파일의 시작(처음)을 기준으로 오프셋 이동 (offset >= 0)

  • SEEK_CUR (1): 파일의 현재 읽기/쓰기 위치를 기준으로 상대적 이동

  • SEEK_END (2): 파일의 끝(EOF)을 기준으로 역방향 이동 (offset <= 0)

  • [핵심 개념 구분]

    • 파일 포인터 (File Offset Pointer): 파일 내부에서 읽기/쓰기가 일어날 바이트 위치를 가리키는 정수형 커서입니다. (fseek으로 제어)
    • FILE * (스트림 구조체 포인터): 유저 공간 힙 메모리에 할당된 파일 제어 구조체(버퍼, OS 핸들, 상태 플래그 등)의 메모리 주소값입니다.
  • 파일 크기 측정 및 원복 패턴:

FILE *fp = fopen("data.bin", "rb");
if (fp != NULL) {
    fseek(fp, 0, SEEK_END);      // 포인터를 파일 맨 끝으로 이동
    long file_size = ftell(fp);  // 시작점부터 끝점까지의 총 바이트 수 반환
    rewind(fp);                  // fseek(fp, 0, SEEK_SET)과 동일 (시작 위치로 원복)

    printf("파일 총 크기: %ld bytes\n", file_size);
    fclose(fp);
}
  • 대용량 파일 주의: 2GB 이상의 대용량 파일을 다룰 때는 long(32비트 환경 2GB 제한) 대신 64비트 오프셋 함수(_fseeki64 / fseeko, off_t)를 사용해야 오버플로우가 발생하지 않습니다.

2. fopen()과 fclose() 1:1 라이프사이클

  • fopen()을 호출하면 유저 메모리(FILE 구조체 및 4KB 버퍼)와 커널 리소스(File Descriptor / Handle / File Lock)가 동시에 할당됩니다.

  • 따라서 사용이 끝나면 반드시 fclose()를 호출하여 자원을 반납해야 합니다.

    • fopen() 호출 시: [User] FILE 구조체 + 4KB 버퍼 할당 / [Kernel] FD 테이블 인덱스 부여 & File Lock 적용
    • fclose() 호출 시: 유저 버퍼 Flush 메모리 free OS FD 반환 및 Lock 해제

3. fclose() 누락 시 발생하는 4대 장애

  1. 파일 디스크립터 누수 (FD Leak):

    • OS가 프로세스당 열 수 있는 파일 핸들 제한(ulimit -n, 보통 1024개)을 초과하게 됩니다.
    • EMFILE (Too many open files) 에러가 발생하며 이후 새로운 파일 오픈 및 네트워크 소켓 통신이 전부 차단됩니다.
  2. 메모리 누수 (Memory Leak):

    • 스트림 제어 블록과 4KB 버퍼 메모리가 힙에 잔류하여 시스템 메모리를 낭비합니다.
  3. 데이터 유실 (버퍼 미반영):

    • 4KB 버퍼에 남아 있던 데이터가 디스크로 완전히 내려쓰이지(Flush) 않은 상태에서 프로세스가 종료되면 기록 중이던 데이터가 증발합니다.
  4. 파일 락(Lock) 지속:

    • OS 레벨의 파일 잠금이 풀리지 않아 외부 프로그램이나 다른 스레드에서 해당 파일을 열거나 수정·삭제할 수 없게 됩니다.

4. 파일 읽기 반복문 안티패턴 (feof 오용 방지)

  • feof() 함수는 EOF에 도달했을 때 즉시 참이 되는 것이 아니라, EOF를 넘어서 읽기를 시도했다가 실패한 후에 플래그가 설정됩니다.
  • 따라서 while (!feof(fp)) 형태로 작성하면 루프가 한 번 더 실행되어 마지막 데이터가 2번 처리되는 버그가 발생합니다.
// [잘못된 패턴] feof는 읽기 실패 후 세팅되므로 마지막 데이터가 2번 중복 처리됨
while (!feof(fp)) {
    fgets(buf, sizeof(buf), fp);
    printf("%s", buf);
}

// [올바른 패턴] 읽기 함수의 반환값(NULL 또는 읽은 바이트 수)으로 루프 조건 검증
while (fgets(buf, sizeof(buf), fp) != NULL) {
    printf("%s", buf);
}